var
	//stores info about animation and about its progress
	Animation = function(obj, attr, options) {
		var name;

		//check if attributes are animatable
		for (name in attr) {
			if (obj.animatableAttrs.indexOf(name) === -1) {
				throw new Error("Attribute " + name + " is not animatable.");
			}
		}

		this.obj = obj;
		this.modification = attr;
		this.duration = Animation.getDuration(options.duration);

		if (typeof options.complete === "function") {
			this.complete = options.complete;
		}

		if (typeof options.repeat === "number" || typeof options.repeat === "string") {
			this.repeats = +options.repeat;
		}
	};

//returns duration in milliseconds
Animation.getDuration = function(duration) {
	if (typeof duration === "number") {
		return duration;
	} else if (duration === "slow") {
		return 600;
	} else if (duration === "fast") {
		return 200;
	} else if (typeof duration === "string") {
		return parseFloat(duration, 10);
	} else {
		return 400;
	}
};

Animation.prototype = {

	//stores reference to animated object
	obj: null,

	//reference to next animation
	after: null,

	//stores
	modification: null,

	//stores initial state of attributes at the animation beginning
	startAttrs: null,

	//stores desired state of attributes at the animation end
	endAttrs: null,

	//stores duration of animation
	duration: -1,

	//stores time when animation was started
	started: -1,

	//stores time when animation should finish
	ending: -1,

	//stores if animation already finished
	finished: false,

	//stores function to be executed when animation finishes
	complete: null,

	//stores how many times should be animation repeated
	repeats: 1,

	//stores total duration of pauses
	pauseDuration: 0,

	//stores when current pause was started
	pauseStarted: -1,

	//starts animation
	start: function(time) {
		if (time === undefined) {
			time = sCanvas.now();
		}

		this.started = time;
		this.ending = this.started + this.duration;

		this.computeAttrs();
	},

	//sets start and end attribute values
	computeAttrs: function() {
		var name, value, computedValue;

		//if attrs are already computed, do not compute them again
		if (this.startAttrs !== null) {
			return;
		}

		this.startAttrs = { };
		this.endAttrs = { };
		
		for (name in this.modification) {
			this.startAttrs[name] = this.obj.attr(name);

			value = this.modification[name];

			if (typeof value === "string") {
				if (value.charAt(1) === "=") {
					if (value.charAt(0) === "+") {
						this.endAttrs[name] = this.startAttrs[name] + parseFloat(value.slice(2), 10);
					} else if (value.charAt(0) === "-") {
						this.endAttrs[name] = this.startAttrs[name] - parseFloat(value.slice(2), 10);
					} else {
						this.endAttrs[name] = this.startAttrs[name];
					}
				} else {
					this.endAttrs[name] = parseFloat(value, 10);
				}
			} else if (typeof value === "number") {
				this.endAttrs[name] = value;
			} else if (typeof value === "function") {
				computedValue = value.apply(this.obj, [ this.startAttrs[name] ]);
				if (typeof computedValue === "number") {
					this.endAttrs[name] = computedValue;
				} else {
					this.endAttrs[name] = this.startAttrs[name];
				}
			} else {
				this.endAttrs[name] = this.startAttrs[name];
			}
		}
	},

	//counts object attributes for next animation frame
	next: function(time) {
		var name, result;

		//if animation is currently pause, adjust time
		if (this.isPaused()) {
			time = this.pauseStarted;
		}

		if (time > this.ending) {
			return this.finish();
		} else {
			result = { };
			for (name in this.endAttrs) {
				result[name] = this.startAttrs[name] + (this.endAttrs[name] - this.startAttrs[name]) * ((time - this.started - this.pauseDuration) / this.duration);
			}
			return result;
		}
	},

	//immediatelly finishes animation
	finish: function() {
		this.finished = true;
		return this.endAttrs;
	},

	//returns true if animation already finished
	isFinished: function() {
		return this.finished;
	},

	//returns true if animations repeats
	isRepeated: function() {
		return this.repeats > 1;
	},

	//restarts animation and lowers animation repetition
	repeat: function(stopped) {
		if (this.isRepeated()) {
			this.finished = false;
			this.pauseDuration = 0;
			this.pauseStarted = -1;
			this.startAttrs = null;
			this.start(stopped === true ? undefined : this.ending);
			this.repeats--;
		}
	},

	//pauses animation
	pause: function() {
		if (!this.isPaused()) {
			this.pauseStarted = sCanvas.now();
		}
	},

	//ends pause
	play: function() {
		var p;
		if (this.isPaused()) {
			p = sCanvas.now() - this.pauseStarted;
			this.pauseDuration += p;
			this.ending += p;
			this.pauseStarted = -1;
		}
	},

	//returns true if animation is paused
	isPaused: function() {
		return this.pauseStarted !== -1;
	}

};
